package bank.impl;

import java.util.Date;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import bank.RemoteBankService;
import bank.RemoteBankService.LocalBankService;
import bank.impl.entities.Account;
import bank.impl.entities.Transfer;
import bank.msgs.BankTransfer;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class BankServiceImpl implements RemoteBankService, LocalBankService {
	@PersistenceContext(unitName = "bank")
	EntityManager em;

	Account retrievedFromAccount;
	Account retrievedToAccount;

	@Override
	public void validateTransferRequest(BankTransfer bankTransfer)
			throws Exception {

		// Get account numbers
		retrieveAccounts(bankTransfer);

		// Check if user's balance is lower than requested transfer amount
		if (retrievedFromAccount.getBalance() < bankTransfer
				.getTransferAmount()) {
			throw new Exception("User has insufficient funds.");
		}

	}

	@Override
	public Long requestTransfer(BankTransfer bankTransfer) throws Exception {

		Double transferAmount = bankTransfer.getTransferAmount();

		// Get account numbers
		retrieveAccounts(bankTransfer);

		// Check if user's balance is lower than requested transfer amount
		if (retrievedFromAccount.getBalance() < transferAmount) {
			throw new Exception("User has insufficient funds.");
		}

		// Update both user's accounts
		retrievedFromAccount.setBalance(retrievedFromAccount.getBalance()
				- transferAmount);
		retrievedToAccount.setBalance(retrievedToAccount.getBalance()
				+ transferAmount);

		// Persist from-account
		try {
			em.persist(retrievedFromAccount);
		} catch (Exception e) {
			throw new Exception("Error updating from-account balance.", e);
		}

		// Persist to-account
		try {
			em.persist(retrievedToAccount);
		} catch (Exception e) {
			throw new Exception("Error updating to-account balance.", e);
		}

		// Create new transfer
		Transfer newTransfer = new Transfer();

		newTransfer.setFromAccount(retrievedFromAccount);
		newTransfer.setToAccount(retrievedToAccount);
		newTransfer.setAmount(transferAmount);
		newTransfer.setTimestamp(new Date(System.currentTimeMillis()));

		// Persist the new transfer
		try {
			em.persist(newTransfer);
		} catch (Exception e) {
			throw new Exception("Error creating new transfer.", e);
		}

		return newTransfer.getTransferId();

	}

	@Override
	public void validateTransfer(BankTransfer bankTransfer) throws Exception {

		Transfer retrievedTransfer;
		long transferId = bankTransfer.getTransferId();

		if (transferId == 0) {
			throw new Exception("Transfer not specified.");
		}

		// Get account numbers
		retrieveAccounts(bankTransfer);

		Query queryTransfer = em.createNamedQuery("transfer.retrieveTransfer");
		queryTransfer.setParameter("transferId", transferId);
		try {
			retrievedTransfer = (Transfer) queryTransfer.getSingleResult();
		} catch (NoResultException e) {
			throw new Exception("No transfer found.");
		}

		if ((retrievedTransfer.getFromAccount().getAccountId() != retrievedFromAccount
				.getAccountId())
				|| (retrievedTransfer.getToAccount().getAccountId() != retrievedToAccount
						.getAccountId())
				|| (retrievedTransfer.getAmount() != bankTransfer
						.getTransferAmount())) {
			throw new Exception("Transfer is not OK.");
		}

	}

	private void retrieveAccounts(BankTransfer bankTransfer) throws Exception {

		// Retrieve account numbers for both accounts
		retrievedFromAccount = em.find(Account.class, bankTransfer.getFromAccountId());
		
		// To-account
		retrievedToAccount = em.find(Account.class, bankTransfer.getToAccountId());

	}

}
